
#include "util.hpp"
#include "log.hpp"

class ServerTcp; // 声明一下

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clientIp_;
    int sock_;
    ServerTcp *this_;

    ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
        : clientPort_(port), clientIp_(ip), sock_(sock), this_(ts)
    {
    }
};

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1)
    {
    }

    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            logMessage(FATAL, "socket:%s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket:%s,%d", strerror(errno), listenSock_);

        // 2. bind
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2 本地socket信息，写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind:%s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind:%s,%d", strerror(errno), listenSock_);

        // 3. 监听socket，为何要监听呢？tcp是面向连接的！
        if (listen(listenSock_, 5) < 0)
        {
            logMessage(FATAL, "bind:%s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen:%s,%d", strerror(errno), listenSock_);
    }

    void loop()
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4. 获取连接，accept的返回值是一个新的socket fd
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                // 获取连接失败，继续获取
                logMessage(WARINING, "accept:%s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept:%s | %s[%d], socket fd:%d", strerror(errno), peerIp.c_str(), peerPort, serviceSock);

            // 5. 提供服务，echho -> 小写 -> 大写
            // v2版本 - 多线程
            // 多线程不需要关闭文件描述符，因为多线程会共享文件描述符表！
            ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, (void *)td);
        }
    }

    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->this_->transService(td->sock_, td->clientIp_, td->clientPort_);
        delete td;
    }

    // 大小写转换服务
    // TCP & UDP : 支持全双工
    void transService(int sock, const std::string &clientIp, uint16_t clientPort)
    {
        assert(sock >= 0);
        assert(!clientIp.empty());
        assert(clientPort >= 1024);

        char inbuffer[BUFFER_SIZE];
        while (true)
        {
            ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1);
            if (s > 0)
            {
                inbuffer[s] = '\0';
                if (strcasecmp(inbuffer, "quti") == 0)
                {
                    logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                    break;
                }
                logMessage(DEBUG, "trans before:%s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
                // 将小写字符转换为大写
                for (int i = 0; i < s; i++)
                {
                    if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                        inbuffer[i] = toupper(inbuffer[i]);
                }
                logMessage(DEBUG, "trans after:%s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
                write(sock, inbuffer, strlen(inbuffer));
            }
            else if (s == 0)
            {
                logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            else
            {
                logMessage(DEBUG, "%s[%d] - read:%s", clientIp.c_str(), clientPort, strerror(errno));
                break;
            }
        }
        // 只要走到这里，一定是client退出了，服务到此结束
        close(sock); // 如果一个进程对应的文件fd，打开了没有被归还，文件描述符泄漏！
        logMessage(DEBUG, "server close %d done", sock);
    }

private:
    int listenSock_;
    uint16_t port_;
    std::string ip_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " prot ip" << std::endl;
    std::cerr << "Usage:\n\t" << proc << " 8080 127.0.0.1\n"
              << std::endl;
}

// ./serverTcp local_port local_ip
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
        ip = argv[2];

    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}